package com.fast.exception.handler;

import com.fast.enums.CodeEnum;
import com.fast.exception.BusinessException;
import com.fast.pojo.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.yaml.snakeyaml.constructor.DuplicateKeyException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author ruan cai yuan
 * @version 1.0
 * @fileName com.fast.exception.handler.ExceptionHandler
 * @description: 统一异常处理 注解@ResponseStatus(Httpstatus.0K):表示统一设置http状态码为200，只通过业务code进行区分;
 * 也避免axios走到onRejected的回调
 * @since 2024/6/22 1:06
 */
@RestControllerAdvice
@ResponseStatus(HttpStatus.OK)
@Slf4j
public class MvcExceptionHandler {

    /**
     * 处理BindException类型的异常
     * 出现时机: Content-Type:application/x-www-form-urlencoded
     */
    @ExceptionHandler(BindException.class)
    public AjaxResult handlerBindException(BindException e) {

        log.error("参数绑定异常", e);
        //获取没有通过校验的字段详情
        List<FieldError> fieldErrors = e.getFieldErrors();
        return AjaxResult.error("参数绑定异常", fieldErrors);
    }

    /**
     * 处理MethodArgumentNotValidException类型的异常
     * 出现时机: Content-Type:application/json + 后台使用  @RequestBody
     */

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public AjaxResult handlerBindException(MethodArgumentNotValidException e) {

        log.error("入参校验失败", e);
        BindingResult bindingResult = e.getBindingResult();
        // 获取到没通过校验的字段详情
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        return AjaxResult.error("入参校验失败", spliceErrMsg(fieldErrors));
    }

    /**
     * 注解 @Validated 写在类上 + 入参带有jsr303注解校验出错
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public AjaxResult handlerBindException(ConstraintViolationException e) {

        log.error("违反约束", e);
        Set<ConstraintViolation<?>> set = e.getConstraintViolations();
        String str = set.stream().map(v -> "属性: " + v.getPropertyPath() + ",传来的值是: " + v.getInvalidValue() + ", 校验不通过, 原因: " + v.getMessage()).collect(Collectors.joining(";"));
        return AjaxResult.error(str);
    }

    /**
     * 参数非法
     */
    @ExceptionHandler(IllegalAccessException.class)
    public AjaxResult handlerBindException(IllegalAccessException e) {

        log.error("参数非法", e);
        return AjaxResult.error(CodeEnum.BAD_REQUEST.getCode(), CodeEnum.BAD_REQUEST.getMsg());
    }

    /**
     * 数据库异常
     */
    @ExceptionHandler(BadSqlGrammarException.class)
    public AjaxResult handlerBindException(BadSqlGrammarException e) {

        log.error("数据库异常", e);
        return AjaxResult.error(CodeEnum.DATABASE_ERROR.getCode(), CodeEnum.DATABASE_ERROR.getMsg());
    }

    /**
     * 唯一性约束校验失败
     */
    @ExceptionHandler(DuplicateKeyException.class)
    public AjaxResult handlerBindException(DuplicateKeyException e) {

        log.error("唯一性约束校验失败", e);
        return AjaxResult.error(CodeEnum.DUPLICATE_KEY_ERROR.getCode(), CodeEnum.DUPLICATE_KEY_ERROR.getMsg());
    }

    /**
     * 业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public AjaxResult handlerBindException(BusinessException e) {

        log.error("业务异常", e);
        return AjaxResult.error(CodeEnum.BUSINESS_ERROR.getCode(), CodeEnum.BUSINESS_ERROR.getMsg());
    }

    /**
     * 处理未精准的异常
     */
    @ExceptionHandler(Exception.class)
    public AjaxResult handlerBindException(Exception e) {

        log.error("处理未精准的异常", e);
        return AjaxResult.error(CodeEnum.ERROR.getCode(), CodeEnum.ERROR.getMsg());
    }

    private String spliceErrMsg(List<FieldError> fieldErrors) {
        return fieldErrors.stream().map(v -> "属性: " + v.getField() + ",传来的值是: " + v.getRejectedValue() + ", 校验不通过, 原因:  " + v.getDefaultMessage()).collect(Collectors.joining(";"));
    }
}